﻿using System.Linq;
using System.Diagnostics;

namespace System.Management.HyperV
{
    /// <summary>
    /// Provides common WMI support for classes ImageManagementService, VirtualSwitchManagementService and VirtualSystemManagementService.
    /// </summary>
    /// <remarks>http://msdn.microsoft.com/en-us/library/cc723869(VS.85).aspx</remarks>
    public abstract class ManagementServiceBase : IDisposable
    {
        protected const string WmiNamespace = @"root\virtualization";
        private string _WmiClassName = null;

        #region WmiPath
        protected ManagementPath WmiPath
        {
            get
            {
                return new ManagementPath()
                {
                    NamespacePath = WmiNamespace,
                    ClassName = _WmiClassName
                };
            }
        }
        #endregion

        #region ManagementServiceInstance
        private ManagementObject _ManagementServiceInstance = null;
        protected ManagementObject ManagementServiceInstance
        {
            get { return _ManagementServiceInstance; }
        }
        #endregion

        #region Constructor
        protected ManagementServiceBase(string wmiClassName)
        {
            this._WmiClassName = wmiClassName;

            using (var mc = new ManagementClass(WmiPath))
            {
                var imsInstanceCollection = mc.GetInstances();

                Debug.Assert(imsInstanceCollection.Count == 1, "Expecting only one instance of a ManagementService to be returned.");

                this._ManagementServiceInstance = imsInstanceCollection.OfType<ManagementObject>().First();
            }
        }
        #endregion

        #region Finalize & Dispose
        public void Dispose()
        {
            try
            {
                if (this.ManagementServiceInstance != null)
                {
                    this.ManagementServiceInstance.Dispose();
                }
            }
            finally
            {
                GC.SuppressFinalize(this);
            }
        }
        ~ManagementServiceBase()
        {
            this.Dispose();
        }
        #endregion

        /// <summary>
        /// Maximum number of seconds to wait for calls to WMI.
        /// </summary>
        protected int Timeout = 30;

        #region InvokeMethod
        private InvokeMethodOptions _InvokeMethodOptions
        {
            get
            {
                return new InvokeMethodOptions() { Timeout = TimeSpan.FromSeconds(this.Timeout) };
            }
        }
        internal InvokeResult InvokeMethod(string methodName, ManagementBaseObject inParams, bool waitForCompleted)
        {
            var result = InvokeMethod(methodName, inParams);

            if (!waitForCompleted)
            {
                return result;
            }
            else // wait for ReturnCode.Completed
            {
                if (result.ReturnCode == ReturnCode.Completed)
                {
                    return result;
                }

                if (result.Job == null)
                {
                    throw new HyperVException("ReturnCode != Completed but also no Job returned! ReturnCode == " + result.ReturnCode.ToString());
                }
                else
                {
                    result.Job.WaitOn(JobState.Starting, JobState.Running);

                    if (result.Job.State == JobState.Completed)
                    {
                        return result;
                    }

                    if (result.Job.State == JobState.Exception)
                    {
                        throw new HyperVException(result.Job);
                    }

                    string exceptionMessage = "Job State did not transition to 'Completed' from 'Starting' or 'Running'. Encountered state: " + result.Job.State.ToString();
                    throw new HyperVException(result.Job);
                }
            }
        }
        internal InvokeResult InvokeMethod(string methodName, ManagementBaseObject inParams)
        {
            var invokeResult = new InvokeResult();

            var moResult = this.ManagementServiceInstance.InvokeMethod(methodName, inParams, _InvokeMethodOptions);

            DebugDumpProperties(moResult);

            if (moResult.Properties.OfType<PropertyData>().Select<PropertyData, string>(pd => pd.Name).Contains("Job") &&
                moResult.Properties["Job"].Value != null)
            {
                invokeResult.Job = new Job(new ManagementObject(moResult.Properties["Job"].Value.ToString()));
                DebugDumpRelated(invokeResult.Job.RawJob);
            }

            invokeResult.ReturnCode = (ReturnCode)moResult.Properties["ReturnValue"].Value;

            if (moResult.Properties.Count > 0)
            {
                invokeResult.OutParameters = moResult.Properties;
            }

            return invokeResult;
        }
        #endregion

        #region Helper Members for Discovery
        public string[] GetMethodNames()
        {
            using (var mc = new ManagementClass(WmiPath))
            {
                return mc.Methods
                            .OfType<MethodData>()
                                .Select<MethodData, string>(src => src.Name)
                                    .ToArray<string>();
            }
        }

        public string[] GetMethodParameters(string methodName)
        {
            using (var mc = new ManagementClass(WmiPath))
            {
                var meth = mc.Methods.OfType<MethodData>().Where<MethodData>(m => m.Name == methodName).First<MethodData>();

                if (meth.InParameters != null)
                {
                    return
                        meth.InParameters.Properties
                            .OfType<PropertyData>()
                                .Select<PropertyData, string>(pd => pd.Name)
                                    .ToArray<string>();

                    //foreach (PropertyData pd in meth.InParameters.Properties)
                    //{

                    //}
                }
                return new string[0];
            }
        }
        public string[] GetMethodReturnValues(string methodName)
        {
            using (var mc = new ManagementClass(WmiPath))
            {
                var meth = mc.Methods.OfType<MethodData>().Where<MethodData>(m => m.Name == methodName).First<MethodData>();

                if (meth.OutParameters != null)
                {
                    return
                        meth.OutParameters.Properties
                            .OfType<PropertyData>()
                                .Select<PropertyData, string>(pd => pd.Name)
                                    .ToArray<string>();

                    //foreach (PropertyData pd in meth.InParameters.Properties)
                    //{

                    //}
                }
                return new string[0];
            }
        }
        [Conditional("DEBUG")]
        public void DebugDumpProperties(ManagementBaseObject mo)
        {
            Debug.WriteLine(string.Format("ManagementBaseObject ClassPath: {0}", mo.ClassPath.Path));
            Debug.Indent();
            foreach (PropertyData pd in mo.Properties)
            {
                Debug.WriteLine(string.Format("Property Name: {0}, Value: '{1}'", pd.Name, pd.Value));
            }
            Debug.Indent();
            Debug.WriteLine(string.Empty.PadRight(50, '-'));
            Debug.WriteLine(mo.GetText(TextFormat.Mof));
            Debug.WriteLine(string.Empty.PadRight(50, '-'));
            Debug.Unindent();
            Debug.Unindent();
        }
        [Conditional("DEBUG")]
        private void DebugDumpRelated(ManagementObject managementObject)
        {
            foreach (ManagementObject mo in managementObject.GetRelated())
            {
                Debug.Indent();
                Debug.WriteLine("Class Path: " + mo.ClassPath.Path);
                foreach (ManagementObject moRelated in mo.GetRelated())
                {
                    Debug.WriteLine("Class Path: " + moRelated.ClassPath.Path);
                    Debug.WriteLine(string.Empty.PadRight(50, '-'));
                    Debug.WriteLine(moRelated.GetText(TextFormat.Mof));
                    Debug.WriteLine(string.Empty.PadRight(50, '-'));
                }
                Debug.Unindent();
            }
        }
        #endregion

    }
}
